package com.sec.android.app.myfiles.operation;

import android.app.FragmentManager;
import android.content.ClipData;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.text.TextUtils;
import android.widget.Toast;

import com.sec.android.app.myfiles.R;
import com.sec.android.app.myfiles.dialog.NameInUseDialogFragment;
import com.sec.android.app.myfiles.dialog.NameInUseDialogFragment.NameInUseDialogCallback;
import com.sec.android.app.myfiles.dialog.UnsupportedNameDialogFragment.UnsupportedNameDialogCallback;
import com.sec.android.app.myfiles.facade.MyFilesFacade;
import com.sec.android.app.myfiles.facade.cmd.CopyMoveCmd;
import com.sec.android.app.myfiles.facade.cmd.DecompressRecordsCmd;
import com.sec.android.app.myfiles.feature.DlpMgr;
import com.sec.android.app.myfiles.feature.DragAndDropMgr;
import com.sec.android.app.myfiles.feature.LockUnlockMgr;
import com.sec.android.app.myfiles.feature.SidesyncMgr;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.log.SamsungAnalyticsLog;
import com.sec.android.app.myfiles.module.ModuleLoader;
import com.sec.android.app.myfiles.module.abstraction.AbsFileOperationImp;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.module.abstraction.FileRecord.StorageType;
import com.sec.android.app.myfiles.module.cloud.CloudFileRecord;
import com.sec.android.app.myfiles.module.local.category.CategoryFileRecord;
import com.sec.android.app.myfiles.module.local.file.LocalFileRecord;
import com.sec.android.app.myfiles.module.optimizestorage.OptimizeStorageDbTableInfo;
import com.sec.android.app.myfiles.module.preview.PreviewCompressDbTableInfo;
import com.sec.android.app.myfiles.navigation.NavigationInfo;
import com.sec.android.app.myfiles.navigation.NavigationManager;
import com.sec.android.app.myfiles.operation.compress.AbsCompressorImp;
import com.sec.android.app.myfiles.operation.compress.AbsCompressorImp.DuplicatedRecordListener;
import com.sec.android.app.myfiles.operation.compress.CompressMgr;
import com.sec.android.app.myfiles.operation.compress.CompressOptions;
import com.sec.android.app.myfiles.provider.DbTableInfo;
import com.sec.android.app.myfiles.provider.PrivateMyFilesProvider;
import com.sec.android.app.myfiles.util.FileUtils;
import com.sec.android.app.myfiles.util.SemFwWrapper;
import com.sec.android.app.myfiles.util.StorageMonitor;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

public class FileOperator extends AsyncTask<Void, Integer, Boolean> implements NameInUseDialogCallback, UnsupportedNameDialogCallback {
    private static FileOperator mInstance = null;
    private static boolean mIsWorking;
    private Context mContext;
    private int mCallerProcessId;
    private AbsFileOperationImp mFileOperationImp = null;
    private FragmentManager mFragmentManager;
    private FileOperationArgs mFileOpArgs;

    public static final int COUNT_PROGRESS = 0;
    private static final int CUR_FILE_PROGRESS = 1;
    private static final int MINIMUM_FREE_SPACE = 26214400; //25MB
    private static final long PROGRESS_POPUP_START_DELAY = 300;
    private static final long PROGRESS_POPUP_END_DELAY = 200;

    /**
     * notify progress of operation to UI.
     */
    private ProgressListener mCountProgressListener = null;
    private ProgressListener mCurFileProgressListener = null;

    private int mCountMax;
    private int mCountProgress;

    private int mCurFileMax;
    private int mCurFileProgress;
    private boolean mCancel;

    private String mProcessingRecordName;
    private FileRecord mCreateDirRecord;

    public static final String CREATE_DIR_BUNDLE = "create_dir_bundle";
    public static final String SKIPPED_FILES = "skipped_files";
    public static final String SIDESYNC_BUNDLE = "sidesync_bundle";
    public static final String POPUP_MSG_BUNDLE = "msg_quota_error_bundle";
    public static final String SHOW_TOAST_ERROR_BUNDLE = "show_toast_error_bundle";
    public static final String HANDLED_SIZE = "handled_size";
    private static final int MEDIA_RROVIDER_MAX = 1000;

    private ArrayList<FileRecord> mHandledSrcList;
    private long mHandledSrcSize;
    private static Operation mOperationType;

    private String mErrorMsg = null;
    private boolean mIsToastHandle;
    private boolean mIsSingleFile = false;

    private Bundle mResultExtras;
    private int mSkippedFiles;

    private ArrayList<FileRecord> mDragItems;
    private NameInUseDialogFragment mNameInUseDialog;

    private static final String TAG = FileOperator.class.getSimpleName();

    FileOperationMediaDbThread mMediaDbThread;

    public enum DuplicatePolicy {
        cancel,
        replace,
        rename,
        none,
    }

    public enum UnsupportedPolicy {
        cancel,
        skip,
        rename,
        none,
    }

    public enum Operation {
        COPY,
        MOVE,
        MOVE_TO_PRIVATE,
        REMOVE_FROM_PRIVATE,
        DELETE,
        RENAME,
        CREATE_DIR,
        COMPRESS,
        DECOMPRESS,
        PREVIEW_COMPRESS,
        UNLOCK,
        CONVERT_DRAG_ITEM,
        BACKUP,
        RESTORE,
        NONE
    }

    public static FileOperator getInstance(int processId, Context context, FragmentManager fragmentManager, FileOperationMediaDbThread dbThread) {
        mInstance = new FileOperator(processId, context, fragmentManager, dbThread);
        return mInstance;
    }

    public static void cancelIfExists() {
        if (mInstance != null) {
            mInstance.cancel();
        }
    }

    public static boolean isWorking() {
        return mIsWorking;
    }

    public static synchronized void init() {
        if (mInstance != null) {
            mInstance.cancel(); // cancel the operation
            mInstance.cancel(true); // notify thread in wait().
            mInstance.mOperationType = Operation.NONE;
        }
        mIsWorking = false;
    }

    public static synchronized void destroy(int processId) {
        if (mInstance != null && mInstance.mCallerProcessId == processId) {
            if (mIsWorking && mInstance.mContext != null) {
                Toast.makeText(mInstance.mContext, mInstance.mContext.getString(R.string.file_operation_cancelled), Toast.LENGTH_SHORT).show();
            }
            init();
            mInstance.cleanUp();
        }
    }

    public FileOperator(int processId, Context context, FragmentManager fragmentManager, FileOperationMediaDbThread dbThread) {
        mContext = context.getApplicationContext();
        mCallerProcessId = processId;
        mFragmentManager = fragmentManager;

        mFileOperationImp = ModuleLoader.getInstance().createFileOperationImp(context, mInternalCurFileProgressListener);
        mCancel = false;
        mIsToastHandle = true;
        mCountMax = 0;
        mCountProgress = 0;
        mMediaDbThread = dbThread;
    }

    public void registerProgressListener(ProgressListener countListener, ProgressListener curFileProgressListener) {
        mCountProgressListener = countListener;
        mCurFileProgressListener = curFileProgressListener;
    }

    public void unregisterProgressListener() {
        mCountProgressListener = null;
        mCurFileProgressListener = null;
    }

    public void cancel() {
        mCancel = true;
        mIsToastHandle = true;
        Log.d(this, "FileOperator::cancel():op=" + mOperationType);
        NavigationInfo naviInfo = NavigationManager.getInstance(mCallerProcessId).getCurInfo();
        SamsungAnalyticsLog.ScreenPath screenPath = (naviInfo != null) ? naviInfo.getScreenPath() : null;

        switch (mOperationType) {
            case UNLOCK:
                LockUnlockMgr.getInstance().cancel();
                break;
            case COMPRESS:
            case DECOMPRESS:
            case PREVIEW_COMPRESS:
                CompressMgr.getInstance(mFileOpArgs.mProcessId).cancel();
                break;
            case COPY:
                if (mFileOpArgs.mViewCloudFile) {
                    SamsungAnalyticsLog.sendLog(mCallerProcessId, SamsungAnalyticsLog.Event.DOWNLOAD_CANCEL, null);
                } else {
                    SamsungAnalyticsLog.sendLog(SamsungAnalyticsLog.ScreenPath.COPY, SamsungAnalyticsLog.Event.COPY_CANCEL, null);
                }
                mFileOperationImp.cancel();
                break;
            case MOVE:
                SamsungAnalyticsLog.sendLog(SamsungAnalyticsLog.ScreenPath.MOVE, SamsungAnalyticsLog.Event.MOVE_CANCEL, null);
                mFileOperationImp.cancel();
                break;
            case DELETE:
                SamsungAnalyticsLog.sendLog(SamsungAnalyticsLog.ScreenPath.DELETE_POPUP, SamsungAnalyticsLog.Event.DELETE_CANCEL, null);
                mFileOperationImp.cancel();
                break;
            default:
                mFileOperationImp.cancel();
                break;
        }
    }

    @Override
    protected void onCancelled() {
        cleanUp();
    }

    public boolean isOperationCancelled() {
        return mCancel;
    }

    public void setOperationArgs(Operation operationType, FileOperationArgs args) {
        mOperationType = operationType;
        mFileOpArgs = args;
    }

    public Operation getOperation() {
        return mOperationType;
    }

    public FileOperationArgs getArgs() {
        return mFileOpArgs;
    }

    @Override
    protected void onPreExecute() {
        mIsWorking = true;
        mSkippedFiles = 0;
    }

    private WakeLock getWakeLock() {
        WakeLock ret = null;
        if (mContext != null) {
            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
            int flag = PowerManager.PARTIAL_WAKE_LOCK;
            ret = pm.newWakeLock(flag, TAG);
        }
        return ret;
    }

    @Override
    protected Boolean doInBackground(Void... params) {
        boolean ret = false;
        int totalFileNum;

        performPreOperation(mOperationType, mFileOpArgs);

        WakeLock wakeLock = getWakeLock();
        if (wakeLock != null) {
            wakeLock.acquire();
        }

        mHandledSrcSize = 0L;

        try {
            switch (mOperationType) {
                case DELETE:
                    if (mFileOpArgs.mSrcList != null) {
                        setProgressMax(COUNT_PROGRESS, getTotalFileCount(mFileOpArgs.mSrcList));
                        mHandledSrcList = new ArrayList<>();
                        ret = performDelete(mFileOpArgs.mClassifiedSrcList);
                    }
                    break;
                case COPY:
                case BACKUP:
                    if (mFileOpArgs.mSrcList != null) {
                        setProgressMax(COUNT_PROGRESS, getTotalFileCount(mFileOpArgs.mSrcList));
                        totalFileNum = mFileOpArgs.mSrcList.size();
                        mIsSingleFile = (totalFileNum == 1);
                        ret = copy(mFileOpArgs.mSrcList, mFileOpArgs.mDst);
                    }
                    break;
                case MOVE:
                    if (mFileOpArgs.mSrcList != null) {
                        setProgressMax(COUNT_PROGRESS, getTotalFileCount(mFileOpArgs.mSrcList));
                        mHandledSrcList = new ArrayList<>();
                        totalFileNum = mFileOpArgs.mSrcList.size();
                        mIsSingleFile = (totalFileNum == 1);
                        ret = move(mFileOpArgs.mSrcList, mFileOpArgs.mDst);
                    }
                    break;
                case RENAME:
                    ret = rename(mFileOpArgs.mSrc, mFileOpArgs.mDst);
                    break;
                case CREATE_DIR:
                    mCreateDirRecord = mkdir(mFileOpArgs.mDst, mFileOpArgs.mCreateFolderName);
                    if (mCreateDirRecord != null) {
                        ret = true;
                    }
                    break;
                case COMPRESS:
                    ret = compress(mFileOpArgs.mSrcList, mFileOpArgs.mDst, mFileOpArgs.mCompressOptions);
                    break;
                case DECOMPRESS:
                    if (mFileOpArgs.mDecompressType == DecompressRecordsCmd.DecompressType.DECOMPRESS_FROM_PREVIEW) {
                        if (mFileOpArgs.mSrcList != null && !mFileOpArgs.mSrcList.isEmpty()) {
                            ret = decompress(mFileOpArgs.mSrcList.get(0), mFileOpArgs.mDst, mFileOpArgs.mDecompressTargetList,
                                    mFileOpArgs.mCompressOptions);
                        }
                    } else {
                        ret = decompress(mFileOpArgs.mSrcList, mFileOpArgs.mDst, mFileOpArgs.mCompressOptions);
                    }
                    break;
                case PREVIEW_COMPRESS:
                    ret = preparePreviewCompress(mFileOpArgs.mSrc);
                    break;
                case UNLOCK:
                    ret = LockUnlockMgr.getInstance().decFile(mContext, mFileOpArgs.mSrc, mInternalCurFileProgressListener);
                    break;
                case CONVERT_DRAG_ITEM:
                    ret = convertDragItem(mFileOpArgs.mClipData);
                    break;
                default:
                    break;
            }
            mErrorMsg = null;
        } catch (FileOperationException e) {
            mErrorMsg = e.getMessage();
            mIsToastHandle = e.isShowToast();
            Log.e(this, mErrorMsg);
            Throwable cause = e.getCause();
            if (cause != null) {
                Log.e(this, cause.toString());
            }
            ret = false;
        }
        if (wakeLock != null && wakeLock.isHeld()) {
            wakeLock.release();
        }
        if (mFileOpArgs.mSrcList != null && mFileOpArgs.mSrcList.size() == mSkippedFiles && mOperationType != Operation.DECOMPRESS) {
            ret = false;
        }
        afterDoInBackground(mOperationType, mFileOpArgs);
        return ret;
    }

    private void afterDoInBackground(Operation operationType, FileOperationArgs fileOpArgs) {
        if (fileOpArgs != null) {
            if (operationType == Operation.DELETE) {
                if (fileOpArgs.mFragment != null &&
                        fileOpArgs.mFragment.getStorageType() == StorageType.OptimizeStorage) {
                    SystemClock.sleep(PROGRESS_POPUP_END_DELAY);
                }
            }
        }
    }

    private void performPreOperation(Operation operationType, FileOperationArgs fileOpArgs) {
        if (mOperationType == Operation.DELETE) {
            if (mFileOpArgs.mSrcList != null && mFileOpArgs.mSrcList.get(0) instanceof CategoryFileRecord) {
                mFileOpArgs.mSrcList = FileUtils.getCategoryFolderRecordList(this, mFileOpArgs.mSrcList);
            }

            if (fileOpArgs.mFragment != null &&
                    fileOpArgs.mFragment.getStorageType() == StorageType.OptimizeStorage) {
                SystemClock.sleep(PROGRESS_POPUP_START_DELAY);
            }
        }

        fileOpArgs.mClassifiedSrcList = classifyRecord(fileOpArgs.mSrcList);
        if (fileOpArgs.mClassifiedSrcList != null && !fileOpArgs.mClassifiedSrcList.isEmpty()) {
            Collection<ArrayList<FileRecord>> srcLists = fileOpArgs.mClassifiedSrcList.values();
            for (ArrayList<FileRecord> srcList : srcLists) {
                mFileOperationImp.preOperation(operationType, srcList.get(0), fileOpArgs.mDst);
            }
        } else {
            mFileOperationImp.preOperation(operationType, fileOpArgs.mSrc, fileOpArgs.mDst);
        }
    }

    private Map<StorageType, ArrayList<FileRecord>> classifyRecord(ArrayList<FileRecord> recordList) {
        Map<StorageType, ArrayList<FileRecord>> ret = null;
        if (recordList != null && !recordList.isEmpty()) {
            boolean isMultipleStorageType = false;
            StorageType storageType = null;
            for (FileRecord record : recordList) {
                if (record != null) {
                    if (storageType == null) {
                        storageType = record.getStorageType();
                    } else if (storageType != record.getStorageType()) {
                        isMultipleStorageType = true;
                        break;
                    }
                }
            }

            ret = new HashMap<>();
            if (isMultipleStorageType) {
                ArrayList<FileRecord> subGroup;
                for (FileRecord record : recordList) {
                    if (record != null) {
                        storageType = record.getStorageType();
                        subGroup = ret.get(storageType);
                        if (subGroup == null) {
                            subGroup = new ArrayList<>();
                            ret.put(storageType, subGroup);
                        }
                        subGroup.add(record);
                    }
                }
            } else {
                ret.put(storageType, recordList);
            }
        }
        return ret;
    }

    /**
     * getTotal files, this method goes all file system and counts the
     * total number of files and directories and then return this value
     */
    protected int getTotalFileCount(ArrayList<FileRecord> recordList) {
        int ret = 0;

        if (recordList != null) {
            ret = recordList.size();

            for (FileRecord record : recordList) {
                if (record != null && record.isDirectory()) {
                    File dir = SemFwWrapper.file(record.getFullPath());
                    ret += getSubFilesCount(dir);
                }
            }
        }

        return ret;
    }

    protected int getSubFilesCount(File curDir) {
        int ret = 0;
        File filesInDir[] = curDir.listFiles();

        if (filesInDir != null && filesInDir.length > 0) {
            ret = filesInDir.length;
            for (File file : filesInDir) {
                if (file.isDirectory()) {
                    ret += getSubFilesCount(file);
                }
            }
        }
        return ret;
    }

    /**
     * get Destination directory record
     *
     * @param src    source file
     * @param dstDir destination directory record
     * @return destination file record
     */
    private FileRecord getDestinationRecord(FileRecord src, FileRecord dstDir) {
        StorageType storageType = dstDir.getStorageType();

        if (dstDir.getStorageType() == StorageType.FolderTree) {
            storageType = StorageType.Local;
        }
        if (src == null) {
            return null;
        }

        FileRecord ret = ModuleLoader.getInstance().createFileRecord(storageType, dstDir.getId(), dstDir.getFullPath(),
                src.getName(), src.getSize(), 0, src.getFileType(), 0, 0, 0);

        if (ret != null) {
            if (ret instanceof CloudFileRecord) {
                ((CloudFileRecord) ret).setDeviceId(((CloudFileRecord) dstDir).getDeviceId());

                ArrayList<String> parentIds = new ArrayList<>();
                parentIds.addAll(((CloudFileRecord) dstDir).getParentIds());
                parentIds.add(((CloudFileRecord) dstDir).getFileId());
                ((CloudFileRecord) ret).setParentIds(parentIds);
            }
        }
        return ret;
    }

    /**
     * Copy Operator method for file list
     *
     * @param src    file list for copy
     * @param dstDir destination folder record
     * @return true/false
     */
    private boolean copy(ArrayList<FileRecord> src, FileRecord dstDir) throws FileOperationException {
        boolean bRet = true;
        if (src == null || src.isEmpty() || dstDir == null) {
            return bRet;
        }

        FileRecord start = src.get(0);
        if (start == null) {
            return bRet;
        }

        if ((start.getStorageType() == StorageType.Cloud) || (dstDir.getStorageType() == StorageType.Cloud)) {
            FileRecord dst = getDestinationRecord(start, dstDir);

            if (mIsSingleFile)
                setProcessingRecordName(start.getName());

            if (dst != null) {
                bRet = mFileOperationImp.copy(src, dst, mFragmentManager, mIsSingleFile, !mFileOpArgs.mViewCloudFile, this);
            } else {
                bRet = false;
            }
        } else {
            for (FileRecord record : src) {
                FileRecord dst = getDestinationRecord(record, dstDir);

                if (dst != null) {
                    bRet = copy(record, dst);
                } else {
                    bRet = false;
                }

                if (!bRet && mDuplicatePolicyApplyAll) {
                    break;
                }
            }
        }

        return bRet;
    }

    /**
     * Move Operator method for file list
     *
     * @param src    file list for move
     * @param dstDir destination folder record
     * @return true/false
     */
    private boolean move(ArrayList<FileRecord> src, FileRecord dstDir) throws FileOperationException {
        boolean bRet = true;
        if (src == null || src.isEmpty() || dstDir == null)
            return true;

        FileRecord start = src.get(0);

        if (start != null) {
            if ((start.getStorageType() == StorageType.Cloud) || (dstDir.getStorageType() == StorageType.Cloud)) {
                FileRecord dst = getDestinationRecord(start, dstDir);

                if (mIsSingleFile)
                    setProcessingRecordName(start.getName());

                if (dst != null) {
                    ArrayList<FileRecord> handledSrcList = ((start.getStorageType() != StorageType.Cloud) && (dstDir.getStorageType() == StorageType.Cloud)) ? mHandledSrcList : null;
                    bRet = mFileOperationImp.move(src, dst, mFragmentManager, mIsSingleFile, this, handledSrcList);
                } else {
                    bRet = false;
                }
            } else {
                for (FileRecord record : src) {
                    FileRecord dst = getDestinationRecord(record, dstDir);

                    if (dst != null) {
                        bRet = move(record, dst);
                    } else {
                        bRet = false;
                    }

                    if (!bRet && mDuplicatePolicyApplyAll) {
                        break;
                    }
                    if (record != null) {
                        mHandledSrcList.add(record);
                        mHandledSrcSize += record.getSize();
                    }
                }
            }
        } else {
            bRet = false;
        }
        return bRet;
    }

    private boolean performDelete(Map<FileRecord.StorageType, ArrayList<FileRecord>> recordMap) throws FileOperationException {
        boolean bRet = true;

        Log.d(this, "perform delete - list:" + (recordMap == null ? "null" : recordMap.size()));
        if (recordMap != null) {
            if (!recordMap.isEmpty()) {
                if (recordMap.containsKey(StorageType.Cloud)) {
                    bRet = deleteCloudRecords(recordMap.remove(StorageType.Cloud));
                }
                if (bRet && recordMap.containsKey(StorageType.Trash)) {
                    bRet = deleteCloudRecords(recordMap.remove(StorageType.Trash));
                }
                Set<StorageType> keySet = recordMap.keySet();
                for (StorageType storageType : keySet) {
                    if (!bRet) {
                        break;
                    }
                    bRet = delete(recordMap.get(storageType));
                }
                setCurProgress(COUNT_PROGRESS, getCurProgress(COUNT_PROGRESS));
            }
        } else {
            bRet = false;
        }

        if (mMediaDbThread != null) {
            mMediaDbThread.clear();
        }
        return bRet;
    }

    /**
     * Delete Operator method for file list
     *
     * @param recordList file list for delete
     * @return true/false
     */
    private boolean delete(ArrayList<FileRecord> recordList) throws FileOperationException {
        boolean bRet = true;

        Log.d(this, "delete - list:" + (recordList == null ? "null" : recordList.size()));
        if (recordList != null) {
            if (!recordList.isEmpty()) {
                int nTotCnt = getProgressMax(COUNT_PROGRESS);
                nTotCnt += recordList.size();

                for (FileRecord record : recordList) {
                    bRet = delete(record);
                    if (bRet) {
                        onFileRecordDeleted(record, nTotCnt);
                        mHandledSrcList.add(record);
                        mHandledSrcSize += record.getSize();
                    }
                }
            }
        } else {
            bRet = false;
        }
        return bRet;
    }

    private void onFileRecordDeleted(FileRecord fileRecord, int totalFileCount) {
        ContentResolver resolver = mContext.getContentResolver();
        if (mFileOpArgs.mFragment != null && fileRecord.getStorageType() == StorageType.Local
                && (mFileOpArgs.mFragment.getStorageType() == StorageType.Category || mFileOpArgs.mFragment.getStorageType() == StorageType.OptimizeStorage)) {
            if (totalFileCount <= MEDIA_RROVIDER_MAX) {
                resolver.delete(FileUtils.MEDIA_PROVIDER_URI, "_id in ( " + fileRecord.getId() + ")", null);
            } else {
                if (mMediaDbThread != null) {
                    mMediaDbThread.setRecordList(fileRecord);
                }
                // Media db updaing time should be less than file system. So add some delay
                SystemClock.sleep(17);
            }
        }

        if (fileRecord.whereAreYouFrom() == StorageType.OptimizeStorage) {
            String selection;
            String[] selectionArgs;
            if (fileRecord.getStorageType() == StorageType.Cloud) {
                selection = "source_media_id=?";
                selectionArgs = new String[]{((CloudFileRecord) fileRecord).getFileId()};
            } else {
                selection = "path like ?";
                selectionArgs = new String[]{fileRecord.getFullPath()};
            }
            Uri.Builder builder = OptimizeStorageDbTableInfo.getInstance().getUriInstance().buildUpon();
            builder.appendQueryParameter(DbTableInfo.QUERY_PARAM_NOTIFY_CHANGE, "false");
            resolver.delete(builder.build(), selection, selectionArgs);
        }
    }

    private boolean deleteCloudRecords(ArrayList<FileRecord> cloudRecords) throws FileOperationException {
        boolean ret = true;
        if (cloudRecords != null && !cloudRecords.isEmpty()) {
            ret = mFileOperationImp.delete(cloudRecords, this);
            if (ret) {
                int cloudRecordsCount = cloudRecords.size();
                for (FileRecord record : cloudRecords) {
                    onFileRecordDeleted(record, cloudRecordsCount);
                    mHandledSrcList.add(record);
                    mHandledSrcSize += record.getSize();
                }
                setCurProgress(COUNT_PROGRESS, getCurProgress(COUNT_PROGRESS) + cloudRecordsCount);
            }
        }
        return ret;
    }

    /**
     * get Destination FileRecord for duplicated file situation. FileRecord is created as user's
     * choice.
     *
     * @param dst dstFileRecord
     * @return new dstRecord as user's choice.
     */
    private synchronized FileRecord getDstFileRecordForDuplicated(FileRecord dst) {
        FileRecord ret = dst;

        if (dst.exists(mContext)) { // if duplicated
            DuplicatePolicy policy = getDuplicatePolicy();

            while (policy == DuplicatePolicy.none) {
                mNameInUseDialog = NameInUseDialogFragment.getInstance(dst, this);
                mNameInUseDialog.showAllowingStateLoss(mFragmentManager, "NameInUse");

                try {
                    wait(); // it'll work as modal dialog.
                } catch (InterruptedException e) {
                    Log.e(this, "InterruptedException:" + e.toString());
                }

                if (mCancel) {
                    policy = DuplicatePolicy.cancel;

                    if (mNameInUseDialog.isAdded()) {
                        mNameInUseDialog.dismissAllowingStateLoss();
                    }
                    break;
                } else {
                    policy = getDuplicatePolicy();
                }
            }

            switch (policy) {
                case rename:
                    ret = mFileOperationImp.getUniqueFileRecord(dst);
                    break;
                case replace: // over write. do nothing
                    break;
                case cancel:
                    ret = null;
                    mSkippedFiles++;
                    break;
                case none:
                default:
                    mCancel = true;
                    break;
            }
        }

        return ret;
    }

    /**
     * Copy Operator method
     *
     * @param src Source file record
     * @param dst destination file record
     * @return true/false
     */
    private boolean copy(FileRecord src, FileRecord dst) throws FileOperationException {
        boolean bRet = false;

        if (!src.exists(mContext)) {
            throw new FileOperationException();
        }

        if (src.isChildDirectory(dst)) {
            throw new FileOperationException(mContext.getResources().getString(R.string.destination_folder_is_subfolder_of_source_folder));
        }

        dst = getDstFileRecordForDuplicated(dst);

        if (!mCancel) {
            setProcessingRecordName(src.getName());
            setCurProgress(COUNT_PROGRESS, getCurProgress(COUNT_PROGRESS) + 1);
            if (dst == null) {
                bRet = true;
            } else {
                if (src.isDirectory()) {
                    FileRecord newDir = dst;
                    if (!dst.exists(mContext) && (dst.getParent() != null)) {
                        newDir = mkdir(dst.getParent(), dst.getName());
                    }

                    if (newDir != null) {
                        ArrayList<FileRecord> recordList = mFileOperationImp.getListInDirectory(src);
                        if (recordList != null) {
                            bRet = copy(recordList, newDir);
                        }
                    }
                } else {
                    bRet = mFileOperationImp.copy(src, dst);
                }
                DlpMgr dlpMgr = DlpMgr.getInstance(mContext);
                if (bRet) {
                    dlpMgr.copyFileExpiry(src, dst);
                }
            }
        } else {
            throw new FileOperationException(mContext.getResources().getString(R.string.file_operation_cancelled));
        }

        return bRet;
    }

    private boolean move(FileRecord src, FileRecord dst) throws FileOperationException {
        boolean bRet;

        if (!src.exists(mContext)) {
            throw new FileOperationException();
        }

        if (src.getPath().equals(dst.getPath())) {
            throw new FileOperationException(mContext.getResources().getString(R.string.destination_folder_is_same_as_source_folder));
        }
        if (src.isChildDirectory(dst)) {
            throw new FileOperationException(mContext.getResources().getString(R.string.destination_folder_is_subfolder_of_source_folder));
        }

        dst = getDstFileRecordForDuplicated(dst);

        if (!mCancel) {
            setProcessingRecordName(src.getName());
            if (dst == null) {
                bRet = true;
            } else {
                bRet = rename(src, dst);

                if (!bRet) {
                    if (src.isDirectory()) {
                        FileRecord newDir = dst;
                        if (!dst.exists(mContext) && (dst.getParent() != null)) {
                            newDir = mkdir(dst.getParent(), dst.getName());
                        }

                        if (newDir != null) {
                            ArrayList<FileRecord> recordList = mFileOperationImp.getListInDirectory(src);
                            bRet = move(recordList, newDir);
                        }
                        if (bRet) {
                            delete(src);
                        } else {
                            Log.e(this, "move fail - do not delete src : " + Log.getEncodedMsg(src.getFullPath()));
                        }
                    } else {
                        setCurProgress(COUNT_PROGRESS, getCurProgress(COUNT_PROGRESS) + 1);
                        bRet = mFileOperationImp.move(src, dst);
                    }
                } else {
                    setCurProgress(CUR_FILE_PROGRESS, 100);
                    setCurProgress(COUNT_PROGRESS, getCurProgress(COUNT_PROGRESS) + 1);
                }
            }
        } else {
            throw new FileOperationException(mContext.getResources().getString(R.string.file_operation_cancelled));
        }

        return bRet;
    }

    private boolean isSupportSubDir(FileRecord record) {
        boolean bRet = true;
        StorageType storageType = record.getStorageType();
        switch (storageType) {
            case Cloud:
            case Shortcut:
                bRet = false;
                break;
            default:
                break;
        }
        return bRet;
    }

    private boolean delete(FileRecord record) throws FileOperationException {
        boolean bRet = false;

        if (!mCancel && (record != null)) {
            if (record.isDirectory() && (isSupportSubDir(record))) {
                ArrayList<FileRecord> recordList = mFileOperationImp.getListInDirectory(record);
                if (recordList != null) {
                    bRet = delete(recordList);
                }
            } else {
                bRet = true;
            }

            if (bRet) {
                setProcessingRecordName(record.getName());
                setCurProgress(COUNT_PROGRESS, getCurProgress(COUNT_PROGRESS) + 1);
                bRet = mFileOperationImp.delete(record);
            }

        } else {
            throw new FileOperationException(mContext.getResources().getString(R.string.file_operation_cancelled));
        }

        return bRet;
    }

    private boolean rename(FileRecord src, FileRecord dst) throws FileOperationException {
        boolean bRet = false;

        if (!mCancel) {
            bRet = mFileOperationImp.rename(src, dst);
        }

        return bRet;
    }

    private FileRecord mkdir(FileRecord dst, String name) throws FileOperationException {
        long freeSpace = 0;
        if (dst.getStorageType() != StorageType.Cloud) {
            freeSpace = StorageMonitor.getStorageFreeSpace(mContext, StorageMonitor.getMatchedStorageType(dst.getFullPath()));
        }
        if ((dst.getStorageType() == StorageType.Cloud) || freeSpace > MINIMUM_FREE_SPACE) {
            return mFileOperationImp.mkdir(dst, name);
        } else if (freeSpace == -1L) {
            //-1 is being sent as a special case when StatFs fails
            //due to storage not available
            //We are sending null here so that not_enough_memory_popup toast does not 
            //come for storage removed case.
            return null;
        } else {
            throw new FileOperationException(mContext.getResources().getString(R.string.not_enough_memory_popup));
        }
    }

    private boolean compress(ArrayList<FileRecord> srcList, FileRecord dst, CompressOptions options) throws FileOperationException {
        boolean bRet = CompressMgr.getInstance(mFileOpArgs.mProcessId).compress(mContext, srcList, dst, options, mInternalCountProgressListener,
                mInternalCurFileProgressListener, new AbsCompressorImp.DuplicatedRecordListener() {
                    @Override
                    public FileRecord handleDuplicatedRecord(FileRecord record) {
                        return getDstFileRecordForDuplicated(record);
                    }
                });
        return bRet;
    }

    private DuplicatedRecordListener getDuplicatedRecordListener() {
        return new DuplicatedRecordListener() {
            @Override
            public FileRecord handleDuplicatedRecord(FileRecord record) {
                return getDstFileRecordForDuplicated(record);
            }
        };
    }

    private boolean decompress(ArrayList<FileRecord> srcList, FileRecord dst, CompressOptions options) throws FileOperationException {
        boolean bRet = CompressMgr.getInstance(mFileOpArgs.mProcessId).decompress(mContext, srcList, dst, options,
                mInternalCountProgressListener, mInternalCurFileProgressListener, getDuplicatedRecordListener());
        return bRet;
    }

    private boolean decompress(FileRecord archiveRecord, FileRecord dst, ArrayList<FileRecord> targetDecompressList, CompressOptions options)
            throws FileOperationException {
        boolean bRet = CompressMgr.getInstance(mFileOpArgs.mProcessId).decompress(mContext, archiveRecord, dst, targetDecompressList, options,
                mInternalCountProgressListener, mInternalCurFileProgressListener, getDuplicatedRecordListener());
        return bRet;
    }

    private boolean preparePreviewCompress(FileRecord src) throws FileOperationException {
        boolean ret = false;
        if (src.getSize() == 0) {
            FileUtils.createFileFromUri(mContext, src.getUri(mContext), new File(src.getFullPath()));
        }
        if (isPreparedPreview(src)) {
            ret = true;
        } else {
            clearPreviewCompress(src);
            AbsCompressorImp.CompressedItem[] list = CompressMgr.getInstance(mFileOpArgs.mProcessId).list(mContext, src);
            if (list != null) {
                setProgressMax(COUNT_PROGRESS, list.length);
                String archivePath = src.getFullPath();
                DbTableInfo tableInfo = PreviewCompressDbTableInfo.getInstance();
                ContentValues[] contentValues = new ContentValues[list.length];
                if (list.length == 0) {
                    ret = true;
                } else {
                    final long UPDATE_PROGRESS_INTERVAL = 50;
                    long updatedTime = 0L;
                    long now;
                    boolean updateProgress;
                    for (int i = 0; i < list.length; i++) {
                        updateProgress = false;
                        now = System.currentTimeMillis();
                        if (now - updatedTime > UPDATE_PROGRESS_INTERVAL || i == (list.length - 1)) {
                            updatedTime = now;
                            updateProgress = true;
                        }
                        if (mCancel) {
                            throw new FileOperationException(mContext.getResources().getString(R.string.file_operation_cancelled));
                        } else {
                            if (updateProgress) {
                                setCurProgress(COUNT_PROGRESS, i + 1);
                                setCurProgress(CUR_FILE_PROGRESS, 0);
                            }
                            contentValues[i] = getPreviewCompressContentValue(tableInfo,
                                    archivePath, src.getSize(), list[i]);
                            if (updateProgress) {
                                setCurProgress(CUR_FILE_PROGRESS, 100);
                            }
                        }
                    }
                    int count = mContext.getContentResolver().bulkInsert(Uri.parse(tableInfo.getUri()), contentValues);
                    ret = count == contentValues.length;
                }
            }
        }
        return ret;
    }

    private ContentValues getPreviewCompressContentValue(DbTableInfo tableInfo, String archivePath,
                                                         long archiveSize, AbsCompressorImp.CompressedItem compressedItem) {
        ContentValues values = new ContentValues();
        values.put(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.ARCHIVE_PATH), archivePath);
        values.put(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.ARCHIVE_ID), String.valueOf(archiveSize));
        values.put(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.PATH), compressedItem.mPath);
        values.put(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.NAME), compressedItem.mName);
        values.put(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.FILE_TYPE), compressedItem.mFileType);
        values.put(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.SIZE), compressedItem.mSize);
        values.put(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.DATE), compressedItem.mDate);
        values.put(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.EXT), compressedItem.mExt);
        values.put(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.ITEM_COUNT), compressedItem.mChildCount);
        values.put(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.ITEM_COUNT_HIDDEN), compressedItem.mHiddenChildCount);
        return values;
    }

    private boolean isPreparedPreview(FileRecord src) {
        boolean prepared = false;
        DbTableInfo tableInfo = PreviewCompressDbTableInfo.getInstance();
        if (src != null) {
            String where = String.format(Locale.getDefault(), "%s=? AND %s='%d'",
                    tableInfo.getColumnName(DbTableInfo.COLUMN_ID.ARCHIVE_PATH), tableInfo.getColumnName(DbTableInfo.COLUMN_ID.ARCHIVE_ID),
                    src.getSize());
            String[] whereArgs = new String[]{
                    src.getFullPath()};
            try (Cursor c = mContext.getContentResolver().query(Uri.parse(tableInfo.getUri()), null, where, whereArgs, null)) {
                if (c != null) {
                    if (c.getCount() > 0) {
                        prepared = true;
                    }
                }
            }
        }
        return prepared;
    }

    private void clearPreviewCompress(FileRecord archiveRecord) {
        DbTableInfo tableInfo = PreviewCompressDbTableInfo.getInstance();
        String where = String.format(Locale.getDefault(), "%s=? AND %s='%d'", tableInfo.getColumnName(DbTableInfo.COLUMN_ID.ARCHIVE_PATH),
                tableInfo.getColumnName(DbTableInfo.COLUMN_ID.ARCHIVE_ID), archiveRecord.getSize());
        String[] whereArgs = new String[]{
                archiveRecord.getFullPath()};
        mContext.getContentResolver().delete(Uri.parse(tableInfo.getUri()), where, whereArgs);
    }

    protected int getProgressMax(int progressType) {
        int nRet = 0;
        if (progressType == COUNT_PROGRESS) {
            nRet = mCountMax;
        } else if (progressType == CUR_FILE_PROGRESS) {
            nRet = mCurFileMax;
        }
        return nRet;
    }

    public void setProgressMax(int progressType, int progressMax) {
        if (progressType == COUNT_PROGRESS) {
            mCountMax = progressMax;
        } else if (progressType == CUR_FILE_PROGRESS) {
            mCurFileMax = progressMax;
        }

        publishProgress(progressType, getCurProgress(progressType), getProgressMax(progressType));
    }

    protected int getCurProgress(int progressType) {
        int nRet = 0;
        if (progressType == COUNT_PROGRESS) {
            nRet = mCountProgress;
        } else if (progressType == CUR_FILE_PROGRESS) {
            nRet = mCurFileProgress;
        }
        return nRet;
    }

    public void setCurProgress(int progressType, int nProgress) {
        if (progressType == COUNT_PROGRESS) {
            mCountProgress = nProgress;
        } else if (progressType == CUR_FILE_PROGRESS) {
            mCurFileProgress = nProgress;
        }

        publishProgress(progressType, getCurProgress(progressType), getProgressMax(progressType));
    }

    protected void setProcessingRecordName(String name) {
        if (name != null) {
            Log.d(this, "setProcessingRecord : " + name);
        } else {
            Log.d(this, "setProcessingRecord : null");
        }

        mProcessingRecordName = name;
    }

    protected String getProcessingRecordName() {
        return mProcessingRecordName;
    }

    @Override
    protected void onProgressUpdate(Integer... value) {
        int progressType = value[0];
        int progress = value[1];
        int total = value[2];

        if (progressType == COUNT_PROGRESS) {
            if (mCountProgressListener != null) {
                mCountProgressListener.onProgressChanged(getProcessingRecordName(), progress, total);
            }
        } else if (progressType == CUR_FILE_PROGRESS) {
            if (mCurFileProgressListener != null) {
                mCurFileProgressListener.onProgressChanged(null, progress, total);
            }
        }
    }

    @Override
    protected void onPostExecute(Boolean result) {
        Log.d(this, "onPostExecute() ] Finished : ret = " + result + " , mOperationType = " + mOperationType);
        int handledCount = (mHandledSrcList != null) ? mHandledSrcList.size() : 0;
        if (mResultExtras == null) {
            mResultExtras = new Bundle();
        }
        switch (mOperationType) {
            case DELETE:
                if (result) {
                    Log.g(handledCount + "_ITEM_DELETED");
                }
                mResultExtras.putLong(HANDLED_SIZE, mHandledSrcSize);
                break;
            case RENAME:
                if (result) {
                    ArrayList<FileRecord> recordList = new ArrayList<>();
                    recordList.add(mFileOpArgs.mSrc);
                }
                break;
            case MOVE:
                MyFilesFacade.addSuggestion(mFileOpArgs.mProcessId, mContext, mFileOpArgs.mDst);
                break;
            case CONVERT_DRAG_ITEM:
                if (result) {
                    MyFilesFacade.copymoveRecord(mFileOpArgs.mProcessId, mContext, Operation.COPY, CopyMoveCmd.OperationProgress.DO_OPERATE,
                            mFileOpArgs.mFragment, mDragItems, mFileOpArgs.mDst, null);
                }
                break;
            case CREATE_DIR:
                if ((mCreateDirRecord != null) && (mCreateDirRecord.getStorageType() == StorageType.Cloud)) {
                    mResultExtras.putString(CREATE_DIR_BUNDLE, ((CloudFileRecord) mCreateDirRecord).getFileId());
                }
                break;
            case COPY:
                if (!mFileOpArgs.mViewCloudFile) {
                    MyFilesFacade.addSuggestion(mFileOpArgs.mProcessId, mContext, mFileOpArgs.mDst);
                }
                break;
            case DECOMPRESS:
                if (mFileOpArgs.mDst != null) {
                    String finalDestinationPath = mResultExtras.getString(AbsCompressorImp.FINAL_DST_PATH, "");
                    if (!finalDestinationPath.isEmpty()) {
                        FileRecord record = FileRecord.createFileRecord(mFileOpArgs.mDst.getStorageType(), finalDestinationPath);
                        if (record != null) mFileOpArgs.mDst = record;
                    }
                }
                break;
            default:
                break;
        }

        if (mFileOpArgs != null) {
            performPostOperation(mOperationType, mFileOpArgs, handledCount);

            if (mFileOpArgs.mClipData != null && mFileOpArgs.mClipData.getDescription().getLabel() != null) {
                if (mFileOpArgs.mClipData.getDescription().getLabel().equals("startSideSyncDrag")) {
                    mResultExtras.putInt(SIDESYNC_BUNDLE, 1);
                }
            }

            if (mCountProgressListener != null) {
                String msg = result ? getSuccessMsg() : getErrorMsg();
                mResultExtras.putInt(SKIPPED_FILES, mSkippedFiles);
                mResultExtras.putBoolean(SHOW_TOAST_ERROR_BUNDLE, mIsToastHandle);
                mCountProgressListener.onFinished(result, msg, mResultExtras);
            }
        }
        mIsWorking = false;
        if (mOperationType == Operation.MOVE || mOperationType == Operation.RENAME) {
            NavigationInfo curInfo = NavigationManager.getInstance(mCallerProcessId).getCurInfo();
            FileRecord dstRecord = null;
            if (curInfo != null) {
                dstRecord = curInfo.getCurRecord(); // in case of "Move to Private", Navigation is not changed to destination.
            } else if (mFileOpArgs != null) {
                dstRecord = mFileOpArgs.mDst;
            }
            if (dstRecord != null) {
                PrivateMyFilesProvider.stopRefreshDb(mContext, dstRecord.getStorageType());
                PrivateMyFilesProvider.refreshDb(mContext, dstRecord, true);
            }
        }

        if ((mOperationType != Operation.CONVERT_DRAG_ITEM) && (mOperationType != Operation.COPY)) {
            mOperationType = Operation.NONE;
        }
        if (mFileOpArgs != null && mFileOpArgs.mClassifiedSrcList != null) {
            mFileOpArgs.mClassifiedSrcList.clear();
        }
        cleanUp();
    }

    private void cleanUp() {
        unregisterProgressListener();
        mMediaDbThread = null;
        mInstance = null;
    }

    private void performPostOperation(Operation operationType, FileOperationArgs fileOpArgs, int handledCount) {
        if (fileOpArgs != null) {
            if (mFileOperationImp != null) {
                if (mFileOpArgs.mClassifiedSrcList != null) {
                    ArrayList<FileRecord> originalSrcList = fileOpArgs.mSrcList;
                    Collection<ArrayList<FileRecord>> values = mFileOpArgs.mClassifiedSrcList.values();
                    for (ArrayList<FileRecord> srcList : values) {
                        fileOpArgs.mSrcList = srcList;
                        mFileOperationImp.postOperation(operationType, fileOpArgs, handledCount);
                    }
                    fileOpArgs.mSrcList = originalSrcList;
                    mFileOpArgs.mClassifiedSrcList.clear();
                } else {
                    mFileOperationImp.postOperation(operationType, fileOpArgs, handledCount);
                }
            }
        }
    }

    private String getSuccessMsg() {
        String msg = "";
        switch (getOperation()) {
            case COMPRESS:
                msg = mContext.getString(R.string.compressed);
                break;
            case MOVE:
                if (CopyMoveCmd.isRemoveFromPrivateOperation()) {
                    int removedFileCount = CopyMoveCmd.getSelectedList().size();
                    msg = mContext.getResources().getQuantityString(R.plurals.items_removed_from_private, removedFileCount, removedFileCount);
                    CopyMoveCmd.setRemoveFromPrivateOperation(false);
                }
                break;
            default:
                msg = "";
                break;
        }
        return msg;
    }

    private String getErrorMsg() {
        if (TextUtils.isEmpty(mErrorMsg)) {
            switch (getOperation()) {
                case DELETE:
                    /*FALL THROUGH */
                case COPY:
                    /*FALL THROUGH */
                case MOVE:
                    boolean hasFolder = false;
                    boolean hasFile = false;
                    int srcCount = 0;
                    if (mFileOpArgs.mSrcList != null) {
                        srcCount = mFileOpArgs.mSrcList.size();
                        if (mSkippedFiles == srcCount) {
                            mErrorMsg = mContext.getString(R.string.file_operation_cancelled);
                        } else {
                            for (FileRecord src : mFileOpArgs.mSrcList) {
                                if (src.isDirectory()) {
                                    hasFolder = true;
                                } else {
                                    hasFile = true;
                                }
                            }
                            mErrorMsg = getQuantityString(getOperation(), srcCount, hasFolder, hasFile);
                        }
                    }

                    break;
                case CREATE_DIR:
                    mErrorMsg = mContext.getString(R.string.failed_to_create_folder);
                    break;
                case RENAME:
                    mErrorMsg = mContext.getString(R.string.failed_to_rename);
                    break;
                default:
                    mErrorMsg = mContext.getString(R.string.error);
                    break;
            }
            if (mFileOpArgs.mDst != null && StorageMonitor.isMountedSdCardPath(mFileOpArgs.mDst.getFullPath()) && StorageMonitor.isSdCardWriteRestricted(mContext)) {
                mErrorMsg = mContext.getString(R.string.security_policy_restricts_use_of_SD_card);
            }

            if (mCancel)
                mErrorMsg = mContext.getString(R.string.file_operation_cancelled);
        }
        return mErrorMsg;
    }

    private String getQuantityString(Operation op, int count, boolean hasFolder, boolean hasFile) {
        Resources res = mContext.getResources();
        String result = null;
        String itemMsg = null;
        int folderMsgResId = -1;
        int fileMsgResId = -1;

        switch (op) {
            case DELETE:
                folderMsgResId = R.plurals.failed_delete_folder;
                fileMsgResId = R.plurals.failed_delete_file;
                if (hasFolder && hasFile) {
                    itemMsg = res.getString(R.string.failed_delete_items);
                }
                break;
            case COPY:
                folderMsgResId = R.plurals.failed_copy_folder;
                fileMsgResId = R.plurals.failed_copy_file;
                if (hasFolder && hasFile) {
                    itemMsg = res.getString(R.string.failed_copy_items);
                }
                break;
            case MOVE:
                folderMsgResId = R.plurals.failed_move_folder;
                fileMsgResId = R.plurals.failed_move_file;
                if (hasFolder && hasFile) {
                    itemMsg = res.getString(R.string.failed_move_items);
                }
                break;
            default:
                break;
        }

        if (hasFolder && hasFile) {
            result = itemMsg;
        } else if (hasFolder) {
            if (folderMsgResId != -1) {
                result = res.getQuantityString(folderMsgResId, count, count);
            }
        } else if (hasFile) {
            if (fileMsgResId != -1) {
                result = res.getQuantityString(fileMsgResId, count, count);
            }
        }
        if (result == null) {
            result = "";
        }

        return result;
    }

    public DuplicatePolicy getDuplicatePolicy() {
        DuplicatePolicy ret = mDuplicatePolicy;

        if (!mDuplicatePolicyApplyAll) {
            mDuplicatePolicy = DuplicatePolicy.none;
        }

        return ret;
    }

    private DuplicatePolicy mDuplicatePolicy = DuplicatePolicy.none;
    private boolean mDuplicatePolicyApplyAll = false;

    /**
     * Implement for exist file name policy
     */
    @Override
    public synchronized void onDuplicateFileSkip(boolean applyAllItems) {
        mDuplicatePolicy = DuplicatePolicy.cancel;
        mDuplicatePolicyApplyAll = applyAllItems;

        notify();
    }

    @Override
    public synchronized void onDuplicateFileReplace(boolean applyAllItems) {
        mDuplicatePolicy = DuplicatePolicy.replace;
        mDuplicatePolicyApplyAll = applyAllItems;

        notify();
    }

    @Override
    public synchronized void onDuplicateFileRename(boolean applyAllItems) {
        mDuplicatePolicy = DuplicatePolicy.rename;
        mDuplicatePolicyApplyAll = applyAllItems;

        notify();
    }

    /**
     * Implementation for unsupported file name
     */
    @Override
    public synchronized void onUnsupportedFileCancel() {
        cancel();
        notify();
    }

    @Override
    public synchronized void onUnsupportedFileSkip() {
        notify();
    }

    @Override
    public synchronized void onUnsupportedFileRename(boolean applyAllItems) {
        notify();
    }

    /**
     * listen the progress of operation from FileOperationImp.
     */
    private ProgressListener mInternalCountProgressListener = new ProgressListener() {
        @Override
        public void onProgressChanged(String fileName, int progress, int total) {
            setProcessingRecordName(fileName);
            if (getCurProgress(COUNT_PROGRESS) != progress) {
                setCurProgress(COUNT_PROGRESS, progress);
            }
            if (getProgressMax(COUNT_PROGRESS) != total) {
                setProgressMax(COUNT_PROGRESS, total);
            }
        }

        @Override
        public void onFinished(boolean bRet, String msg, Bundle extras) {
            mResultExtras = extras;
        }
    };

    private ProgressListener mInternalCurFileProgressListener = new ProgressListener() {
        @Override
        public void onProgressChanged(String fileName, int progress, int total) {
            if (getCurProgress(CUR_FILE_PROGRESS) != progress) {
                setCurProgress(CUR_FILE_PROGRESS, progress);
            }
            if (getProgressMax(CUR_FILE_PROGRESS) != total) {
                setProgressMax(CUR_FILE_PROGRESS, total);
            }
        }

        @Override
        public void onFinished(boolean bRet, String msg, Bundle extras) {
        }
    };

    private boolean convertDragItem(ClipData clipData) {
        boolean ret = true;
        String startedLocation = getDragStartedLocationValue(clipData);
        if (DragAndDropMgr.DRAG_DROP_MYFILES_LOCAL.equalsIgnoreCase(startedLocation)
                || DragAndDropMgr.DRAG_DROP_GALLERY_LOCAL.equalsIgnoreCase(startedLocation)
                || DragAndDropMgr.DRAG_DROP_SOURCE_IS_SECURE_FOLDER.equalsIgnoreCase(startedLocation)) {
            mDragItems = getLocalFileRecordInDrag(clipData, mContext);
        } else if (DragAndDropMgr.DRAG_DROP_MYFILES_CLOUD.equalsIgnoreCase(startedLocation)
                || DragAndDropMgr.DRAG_DROP_GALLERY_CLOUD.equalsIgnoreCase(startedLocation)) {
            mDragItems = getCloudFileRecordInDrag(clipData);
        } else if (DragAndDropMgr.DRAG_DROP_LABEL_SIDE_SYNC.equalsIgnoreCase(startedLocation)) {
            SidesyncMgr.getInstance().onDragSidesyncToList(mContext, mFileOpArgs.mDst);

            ret = false;
        } else {
            Log.e(this, "unsupported drag type:" + startedLocation);
        }
        if (mDragItems == null || mDragItems.isEmpty()) {
            Log.e(this, "There is no item in clip data");
            ret = false;
        }
        return ret;
    }

    private String getDragStartedLocationValue(ClipData clipData) {
        String retValue = null;
        if ((clipData != null) && (clipData.getDescription() != null)) {
            retValue = (String) clipData.getDescription().getLabel();
        }
        return retValue;
    }

    protected static ArrayList<FileRecord> getLocalFileRecordInDrag(ClipData clipData, Context context) {
        ArrayList<FileRecord> selectedRecords = new ArrayList<>();
        if (clipData != null) {
            int itemCount = clipData.getItemCount();

            String selectedFilePath;
            ClipData.Item itemUri;
            Uri uri;

            for (int i = 0; i < itemCount; i++) {
                itemUri = clipData.getItemAt(i);
                selectedFilePath = null;

                if (itemUri != null) {
                    uri = itemUri.getUri();
                    if (uri != null) {
                        selectedFilePath = FileUtils.getFilePathFromUri(context, uri, false);
                    } else {
                        CharSequence uriText = itemUri.getText();
                        if (uriText != null) {
                            selectedFilePath = uriText.toString();
                        }
                    }

                    if (selectedFilePath != null && !TextUtils.isEmpty(selectedFilePath)) {
                        selectedRecords.add(new LocalFileRecord(selectedFilePath));
                    }
                }
            }
        }
        return selectedRecords;
    }

    protected static ArrayList<FileRecord> getCloudFileRecordInDrag(ClipData clipData) {
        ArrayList<FileRecord> selectedRecords = new ArrayList<>();
        if (clipData != null) {
            int itemCount = clipData.getItemCount();

            int deviceId;
            String tmp, parentId, fileId, filename;
            ClipData.Item itemUri;
            Uri uri;

            for (int i = 0; i < itemCount; i++) {
                itemUri = clipData.getItemAt(i);

                if (itemUri != null) {
                    uri = itemUri.getUri();
                    if (uri != null) {
                        tmp = uri.toString();
                        Log.d("MYFILES", "cloud URI : " + tmp);

                        if (!TextUtils.isEmpty(tmp)) {
                            StringTokenizer st = new StringTokenizer(tmp, CloudFileRecord.SEPARATOR);
                            deviceId = Integer.parseInt(st.nextToken());
                            parentId = st.nextToken();
                            fileId = st.nextToken();
                            filename = st.nextToken();

                            selectedRecords.add(new CloudFileRecord(deviceId, parentId, fileId, filename));
                        }
                    }
                }
            }
        }
        return selectedRecords;
    }

    public static Operation getFileOperation() {
        return mOperationType;
    }

    public long getHandledSize() {
        return mHandledSrcSize;
    }

    public void onGlobalLayoutFinish(int height) {
        if (mNameInUseDialog != null) {
            mNameInUseDialog.updateView(height);
        }
    }
}
